from typing import Dict, Any, List
from ..base.plugins import PluginInfo, PluginInvokeParams, PluginObject
from ..base.fields import OPTIONAL_KEY, REQUIRED_KEY
from calc_manager.plugins.plugin_object import PluginHelper
from calc_manager.base.fields import FieldInfo

import logging
logger = logging.getLogger(__name__)

class PluginManager:
    """
    A manager class for handling plugin registration and lookup.

    This class maintains two dictionaries to store plugin information:
    - One indexed by class parameter names
    - One indexed by plugin names

    This allows for efficient plugin lookup by either identifier.
    """
    def __init__(self) -> None:
        """
        Initialize the PluginManager.

        Creates two empty dictionaries to store plugin information:
        - plugins_by_cls_param_name: Maps class parameter names to PluginInfo objects
        - plugins_by_plugin_name: Maps plugin names to PluginInfo objects
        """
        self.plugins_by_cls_param_name: Dict[str, PluginInfo] = {}
        self.plugins_by_plugin_name: Dict[str, PluginInfo] = {}

    def register_plugin(self, plugin_info: PluginInfo) -> None:
        """
        Register a new plugin with the plugin manager.

        This method registers a plugin by storing its information in both the class parameter name
        and plugin name dictionaries. It performs validation to ensure no duplicate plugins exist
        and verifies that the plugin module and class are valid.

        Args:
            plugin_info (PluginInfo): Information about the plugin to register, including:
                - plugin_name: Unique name identifier for the plugin
                - plugin_class_param_name: Class parameter name for the plugin
                - plugin_module_name: Name of the module containing the plugin
                - plugin_class_name: Name of the plugin class

        Raises:
            ValueError: If a plugin with the same name or class parameter name already exists

        Note:
            TODO: Add validation of plugin_info fields using Plugin entry function annotations
        """
        #TODO: check plugin_info fields by Plugin entry func annotation
        if plugin_info.plugin_name in self.plugins_by_plugin_name:
            raise ValueError(f"Plugin with name {plugin_info.plugin_name} already exists")
        if plugin_info.plugin_class_param_name in self.plugins_by_cls_param_name:
            raise ValueError(f"Plugin with param name {plugin_info.plugin_class_param_name} already exists")

        self.plugins_by_cls_param_name[plugin_info.plugin_class_param_name] = plugin_info
        self.plugins_by_plugin_name[plugin_info.plugin_name] = plugin_info
        logger.info(f"Register plugin {plugin_info.plugin_name} with param name {plugin_info.plugin_class_param_name}")

        PluginHelper.check_plugin_module_and_class(plugin_info.plugin_module_name, plugin_info.plugin_class_name)
    def get_plugin_name_by_cls_param_name(self, plugin_cls_param_name: str) -> str:
        """
        Get the plugin name corresponding to a class parameter name.

        Args:
            plugin_cls_param_name (str): The class parameter name to look up

        Returns:
            str: The plugin name associated with the class parameter name

        Raises:
            ValueError: If no plugin is found with the given class parameter name
        """
        plugin_info = self.plugins_by_cls_param_name.get(plugin_cls_param_name)
        if not plugin_info:
            raise ValueError(f"Plugin with param name {plugin_cls_param_name} not found")
        return plugin_info.plugin_name

    def plugin_cls_param_name_is_valid(self, plugin_cls_param_name: str) -> bool:
        """
        Check if a class parameter name corresponds to a valid plugin.

        Args:
            plugin_cls_param_name (str): The class parameter name to validate

        Returns:
            bool: True if the parameter name exists in registered plugins, False otherwise
        """
        return plugin_cls_param_name in self.plugins_by_cls_param_name
    
    def get_plugin_fields_by_plugin_cls_param_name(self, plugin_cls_param_name: str) -> List[FieldInfo]:
        """
        Get the field information for a plugin by its class parameter name.

        Args:
            plugin_cls_param_name (str): The class parameter name of the plugin

        Returns:
            List[FieldInfo]: List of field information objects for the plugin

        Raises:
            ValueError: If no plugin is found with the given class parameter name
        """
        plugin_info = self.plugins_by_cls_param_name.get(plugin_cls_param_name)
        if not plugin_info:
            raise ValueError(f"Plugin with param name {plugin_cls_param_name} not found")
        return list(plugin_info.plugin_fields.values())
    
    def get_plugin_params_invoke_params_by_param_dict(self, plugin_invoke_params: Dict[str, Dict[str, Any]]) -> Dict[str, PluginInvokeParams]:
        """
        Convert a dictionary of plugin parameters into plugin invoke parameter objects.

        Args:
            plugin_invoke_params (Dict[str, Dict[str, Any]]): Dictionary mapping plugin names to their parameter dictionaries

        Returns:
            Dict[str, PluginInvokeParams]: Dictionary mapping plugin names to their invoke parameter objects

        Raises:
            ValueError: If a plugin is not found or if required fields are missing
        """
        # TODO: separate operator plugin params 
        # plugin invoke params key: PLUGINNAME_PARAMNAME
        ret = {}
        logger.debug(f"plugin_invoke_params: {plugin_invoke_params}")
        for plugin_name, params in plugin_invoke_params.items():
            if plugin_name not in self.plugins_by_plugin_name:
                raise ValueError(f"Plugin {plugin_name} not found, available plugins {self.plugins_by_plugin_name.keys()}")
            plugin_info = self.plugins_by_plugin_name.get(plugin_name)
            if not plugin_info:
                raise ValueError(f"Plugin {plugin_name} not found, available plugins {self.plugins_by_plugin_name.keys()}")
            all_fields_name = [field.name for field in plugin_info.plugin_fields.values()]
            required_fields_name = [field.name for field in plugin_info.plugin_fields.values() if field.optional_field == REQUIRED_KEY]

            #check all required fields are in params
            for required_field_name in required_fields_name:
                if required_field_name not in params:
                    raise ValueError(f"Field {required_field_name} is required for plugin {plugin_name}")
            
            for p in params:
                if p not in all_fields_name:
                    raise ValueError(f"Field {p} is not valid for plugin {plugin_name}, available fields: {all_fields_name}")
            
            inv_params = PluginInvokeParams(plugin_name=plugin_name, plugin_cls_param_name=plugin_info.plugin_class_param_name, param=params)
            ret[plugin_name] = inv_params
        return ret

    def get_plugin_object_helper_by_cls_param_name(self, param_name: str, **kwargs) -> PluginHelper:
        """
        Create a plugin helper object for a given class parameter name.

        Args:
            param_name (str): The class parameter name of the plugin
            **kwargs: Additional keyword arguments required by the plugin

        Returns:
            PluginHelper: A helper object for the specified plugin

        Raises:
            ValueError: If the plugin is not found or if required fields are missing
        """
        plugin_info = self.plugins_by_cls_param_name.get(param_name)
        if not plugin_info:
            raise ValueError(f"Plugin with param name {param_name} not found")

        #check kwargs has all required fields
        for _key, field in plugin_info.plugin_fields.items():
            if field.optional_field != OPTIONAL_KEY and field.optional_field != REQUIRED_KEY:
                raise ValueError(f"Field {field.name} has invalid optional_field value")
            if field.optional_field == OPTIONAL_KEY:
                if field.name not in kwargs:
                    kwargs[field.name] = field.default_val
                continue
            if field.optional_field == REQUIRED_KEY and field.name not in kwargs:
                raise ValueError(f"Field {field.name} is required for plugin {plugin_info.plugin_name}")

        logger.debug("Creating plugin object %s with param name %s" % (plugin_info, plugin_info.plugin_class_param_name))
        plugin: PluginHelper = PluginHelper(plugin_info, **kwargs)
        return plugin

    def get_fileds_by_name(self, plugin_name: str) -> Dict[str, FieldInfo]:
        """
        Get field information for a plugin by its name.

        Args:
            plugin_name (str): The name of the plugin

        Returns:
            Dict[str, FieldInfo]: Dictionary mapping field names to their field information

        Raises:
            ValueError: If no plugin is found with the given name
        """
        plugin_info = self.plugins_by_plugin_name.get(plugin_name)
        if not plugin_info:
            raise ValueError(f"Plugin with name {plugin_name} not found")
        return plugin_info.plugin_fields
